package ca

import (
	"blockchains-aio/conf"
	"blockchains-aio/util"
	"errors"
	"fmt"
	fccc "github.com/hyperledger/fabric-ca/cmd/fabric-ca-client/command"
	"github.com/shirou/gopsutil/host"
	"gopkg.in/yaml.v3"
	"log"
	"os"
	"reflect"
	"strings"
	"sync"
)

var (
	once          sync.Once
	fcCooridnator FabricCaCooridnator

	fabricCaNotReadyError error = errors.New(`
Trouble solution:
fabric-ca-server start -d -b admin:adminpw --ca.certfile /path/to/org/pem --ca.keyfile /path/to/org/sk \ 
                               --tls.enabled=false \
                               --cfg.affiliations.allowremove \ 
                               --cfg.identities.allowremove 

export FABRIC_CA_CLIENT_HOME=/path/to/ca/client/home
fabric-ca-client enroll -d -u http://admin:adminpw@francis:7054 
`)
)

type Flags struct {
	FlagPairs map[string]string `json:"flagPairs"`
}

func NewFlags() Flags {
	flags := Flags{}
	flags.FlagPairs = make(map[string]string)

	return flags
}

func (flags Flags) With(k, v string) {
	flags.FlagPairs[k] = v
}

func (flags Flags) GetAttr(k string) string {
	return flags.FlagPairs[k]
}

func (flags Flags) follow(commands []string) []string {
	for k, v := range flags.FlagPairs {
		commands = append(commands, k, v)
	}

	return commands
}

type FabricCaCooridnator struct {
	cac_home    string
	org         string
	orgPath     string
	caServerUrl string
}

type ClientConfig struct {
	URL string `yaml:"url"`
}

// whichOrg: orgcpp.zhaowu.com, orggo.zhaowu.com
func NewCooridnator(cacHome, whichOrg string) *FabricCaCooridnator {
	//fCryptoConfigPath := conf.GetStackTemplateResDir("bft3", "crypto-config")
	//orgPath := fmt.Sprintf("%s/%s/%s", fCryptoConfigPath, "peerOrganizations", whichOrg)
	orgPath := conf.GetPeerOrganization(whichOrg)
	cacH := os.Getenv("FABRIC_CA_CLIENT_HOME")
	if cacH == "" {
		panic("The environmental variable is unavailable: FABRIC_CA_CLIENT_HOME")
	}

	once.Do(func() {
		fcCooridnator = FabricCaCooridnator{
			org:     whichOrg,
			orgPath: orgPath,
		}

		caClientConfFile := fmt.Sprintf("%s%s", cacH, "/fabric-ca-client-config.yaml")

		if util.IsPathExists(caClientConfFile) {
			//fcCooridnator.cac_home = conf.GetCacFolder()
			fcCooridnator.cac_home = cacH

			caServerUrl, err := extractServerUrlFromClientConfiguration(caClientConfFile)
			if err != nil {
				log.Println("extractServerUrlFromClientConfiguration error:")
				panic(err)
			}
			log.Println("raw caServerUrl: ", caServerUrl)

			if strings.HasPrefix(caServerUrl, "https://") {
				caServerUrl, _ = strings.CutPrefix(caServerUrl, "https://")
			} else if strings.HasPrefix(caServerUrl, "http://") {
				caServerUrl, _ = strings.CutPrefix(caServerUrl, "http://")
			}

			log.Printf("caServerUrl: %s,ca home:%s", caServerUrl, cacH)

			fcCooridnator.caServerUrl = caServerUrl
		} else {
			panic("fabric-ca-client-config.yaml can not been found")
		}
	})

	/*
		ifExternalCacHome := false
		cacH := os.Getenv("FABRIC_CA_CLIENT_HOME")
		if cacH != "" {
			ifExternalCacHome = true
		}

		once.Do(func() {
			fcCooridnator = FabricCaCooridnator{
				org:     whichOrg,
				orgPath: orgPath,
			}

			if !ifExternalCacHome {
				caClientConfFile := fmt.Sprintf("%s%s", conf.GetCacFolder(), "/fabric-ca-client-config.yaml")

				if util.IsPathExists(caClientConfFile) {
					fcCooridnator.cac_home = conf.GetCacFolder()
					os.Setenv("FABRIC_CA_CLIENT_HOME", fcCooridnator.cac_home)

					caServerUrl, err := extractServerUrlFromClientConfiguration(caClientConfFile)
					if err != nil {
						panic(err)
					}

					if strings.HasPrefix(caServerUrl, "https://") {
						caServerUrl, _ = strings.CutPrefix(caServerUrl, "https://")
					} else if strings.HasPrefix(caServerUrl, "http://") {
						caServerUrl, _ = strings.CutPrefix(caServerUrl, "http://")
					}

					fcCooridnator.caServerUrl = caServerUrl
				} else {
					panic(fabricCaNotReadyError)
				}

			} else {
				fcCooridnator.cac_home = cacH
			}
		})

		if !ifExternalCacHome && cacHome != "" {
			fcCooridnator.cac_home = cacHome
			os.Setenv("FABRIC_CA_CLIENT_HOME", cacHome)
		}
	*/
	return &fcCooridnator
}

func extractServerUrlFromClientConfiguration(caClientConfFile string) (string, error) {
	caClientConfig := new(ClientConfig)

	file, err := os.Open(caClientConfFile)
	if err != nil {
		return "", err
	}
	defer file.Close()
	decoder := yaml.NewDecoder(file)
	err = decoder.Decode(&caClientConfig)
	if err != nil {
		return "", err
	}

	return caClientConfig.URL, nil
}

// fabric-ca-client register --id.name user4 --id.type client --id.secret pw --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*"
// fabric-ca-client enroll -u http://user4:user4pw@francis:7054 -M /home/francis/fabric-ca/orggo.zhaowu.com/users/user4@orggo.zhaowu.com/msp
func (fcc *FabricCaCooridnator) SignupUser(registerFlags Flags, enrollFlags Flags) error {
	user := registerFlags.GetAttr("--id.name")
	userType := registerFlags.GetAttr("--id.type")

	if checkNameConfict(fcc.orgPath, user, fcc.org, userType) {
		return errors.New(fmt.Sprintf("%s existed", user))
	}

	err := fcc.register(registerFlags)
	if err != nil {
		return errors.Join(errors.New("signup user error:"), err)
	}

	//enrich the parameter of "-u" "-M"
	url := enrollFlags.GetAttr("-u")
	if strings.Index(url, "@") == -1 {
		// 2014-11-27
		//url = fmt.Sprintf("%s@%s:7054", url, getHostInfo())
		url = fmt.Sprintf("%s@%s", url, fcc.caServerUrl)
		enrollFlags.With("-u", url)
	}
	partOfMsp := enrollFlags.GetAttr("-M")
	msp := fcc.orgPath + partOfMsp
	orgMsp := fcc.orgPath + "/msp"
	enrollFlags.With("-M", msp)

	err = fcc.enroll(enrollFlags)
	if err != nil {
		return errors.Join(errors.New("signup user error:"), err)
	}

	// copy the Node OU configuration file into the identity MSP folder
	err = util.CopyFile(orgMsp+"/config.yaml", msp+"/config.yaml")
	if err != nil {
		return errors.Join(errors.New("copy config.yaml error:"), err)
	}

	return nil
}

// fabric-ca-client revoke -e user4 -r unspecified --gencrl
// fabric-ca-client revoke -e zhao.admin -r unspecified --gencrl
func (fcc *FabricCaCooridnator) RevokeCert(revokeFlags Flags) error {

	err := fcc.revoke(revokeFlags)
	if err != nil {
		return errors.Join(errors.New("revoke cert error:"), err)
	}

	return nil
}

func (fcc *FabricCaCooridnator) GetCrl() (string, error) {
	crlRelativePath := "/msp/crls/crl.pem"

	bytes, err := os.ReadFile(fmt.Sprintf("%s%s", fcc.orgPath, crlRelativePath))
	if err != nil {
		return "", errors.Join(errors.New("revoke cert error: read file err:"), err)
	}

	return string(bytes), nil
}

func (fcc *FabricCaCooridnator) InvokeMethod(methodName string, params ...interface{}) error {
	v := reflect.ValueOf(fcc)
	method := v.MethodByName(methodName)
	if !method.IsValid() {
		return errors.New("method not existed")
	}

	values := util.CallMethod(method, params...)
	err, ok := values[0].Interface().(error)
	if ok {
		return errors.New(fmt.Sprintf("unexpected result error: %+v", values[0]))
	}

	return err
}

func checkNameConfict(orgPath, name, org, userType string) bool {
	var nameAtOrg string
	targetPath := orgPath

	if userType == "orderer" {
		targetPath = targetPath + "/orderers"
		nameAtOrg = fmt.Sprintf("%s.%s", name, org)
	} else if userType == "peer" {
		targetPath = targetPath + "/peers"
		nameAtOrg = fmt.Sprintf("%s.%s", name, org)
	} else {
		targetPath = targetPath + "/users"
		nameAtOrg = fmt.Sprintf("%s@%s", name, org)
	}

	return util.IsPathExists(fmt.Sprintf("%s/%s", targetPath, nameAtOrg))
}

// RunMain is the fabric-ca client main
func runMain(args []string) error {
	// Save the os.Args
	saveOsArgs := os.Args
	os.Args = args

	// Execute the command
	cmdName := ""
	if len(args) > 1 {
		cmdName = args[1]
	}
	ccmd := fccc.NewCommand(cmdName)
	err := ccmd.Execute()

	// Restore original os.Args
	os.Args = saveOsArgs

	return err
}

func getHostInfo() string {
	nInfo, _ := host.Info()
	return nInfo.Hostname
}
